package cn.gs.exchanger;

// @author gs
import cn.gs.util.monitor.ITimerMonitorTask;
import cn.gs.util.monitor.TimerMonitor;
import cn.gs.util.net.DefaultSocket;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Exchanger extends DefaultSocket implements Runnable {

    public static int RETRY_TIME = 0;
    public static int TIMEOUT = 60;

    public static void main(String[] args) {
        Logger.getLogger("").setLevel(Level.ALL);
        for (Handler handler : Logger.getLogger("").getHandlers()) {
            handler.setLevel(Level.ALL);
        }
        waitForConnect(ExchangerClient.PORT);
    }

    private static class WaitConnect extends Thread {

        int port;

        WaitConnect(int port) {
            this.port = port;
        }

        @Override
        public void run() {
            try {
                ServerSocket server = new ServerSocket(port);
                ExchangerRouter router = new ExchangerRouter();
                Logger.getLogger(Exchanger.class.getName()).log(Level.INFO, "Listening...{0}", port);
                while (!Thread.interrupted()) {
                    Socket socket = server.accept();
                    Exchanger el = new Exchanger(router);
                    el.setSocket(socket);
                    Thread th = new Thread(el, "Exchanger_" + el.remoteIP());
                    th.start();
                }
            } catch (Throwable ex) {
                Logger.getLogger(Exchanger.class.getName()).log(Level.WARNING, null, ex);
            }
            Logger.getLogger(Exchanger.class.getName()).log(Level.SEVERE, "Exchanger_Listener_Stopped");
        }
    }

    public static void waitForConnect(int port) {
        WaitConnect con = new WaitConnect(port);
        con.start();
    }

    public void onDisconnect(Throwable th) {
        router.removeAll(this);
        super.onDisconnect(null);
    }

    @Override
    public void run() {
        Logger.getLogger(Exchanger.class.getName()).log(Level.INFO, "Link_{0}<->{1}_Start", new String[]{localIP(), remoteIP()});
        while (!Thread.interrupted()) {
            try {
                String msg = readLine();
                process(msg);
            } catch (IOException ex) {
                Logger.getLogger(Exchanger.class.getName()).log(Level.WARNING, "Link_" + localIP() + "<->" + remoteIP() + "_Stopped", ex);
                return;
            } catch (Throwable ex) {
                Logger.getLogger(Exchanger.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        Logger.getLogger(Exchanger.class.getName()).log(Level.SEVERE, "Link_{0}<->{1}_Stopped", new String[]{localIP(), remoteIP()});
    }

    public void writeLine(ExchangerCommand cmd) throws IOException {
        writeLine(cmd.toString());
    }
    private static Map<String, Exchanger> tasks = new HashMap<String, Exchanger>();

    private static void answer(ExchangerCommand cmd) {
        Exchanger oo = tasks.remove(cmd.getNumber());
        if (oo != null) {
            try {
                cmd.sendTo(oo);
            } catch (IOException ex) {
                Logger.getLogger(Exchanger.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    private void sendError(ExchangerException ex, ExchangerCommand cmd) {
        List<String> es = new LinkedList<String>();
        es.add(ex.getType().name());
        es.add(ex.getMessage());
        Throwable th = ex.getCause();
        while (th != null) {
            es.add(th.getClass().getSimpleName());
            es.add(th.getMessage());
            th = th.getCause();
        }
        ExchangerCommand command = cmd.replayError(es);
        try {
            command.sendTo(this);
        } catch (IOException ex1) {
            Logger.getLogger(Exchanger.class.getName()).log(Level.SEVERE, null, ex1);
        }
    }
    static TimerMonitor monitor = new TimerMonitor();

    public void process(String msg) {
        ExchangerCommand cmd = new ExchangerCommand(msg);
        if (cmd.isSystemCommand()) {
            if (cmd.match(ExchangerCommand.SystemCommand.ANSWER) || cmd.match(ExchangerCommand.SystemCommand.ERROR)) {
                answer(cmd);
            } else {
                ExchangerCommand ans = cmd.replayAnswer();
                if (cmd.match(ExchangerCommand.SystemCommand.REGISTER)) {
                    router.reg(cmd.getArgsList(), this);
                } else if (cmd.match(ExchangerCommand.SystemCommand.UNIQUE)) {
                    router.uni(cmd.getArgsList(), this);
                } else if (cmd.match(ExchangerCommand.SystemCommand.DELETE)) {
                    router.del(cmd.getArgsList(), this);
                } else if (cmd.match(ExchangerCommand.SystemCommand.ALL_SERVICE)) {
                    List<String> as = router.allService(cmd.getArgsList());
                    ans = cmd.replayAnswer(as);
                } else if (cmd.match(ExchangerCommand.SystemCommand.SERVICE_PROVIDER_COUNT)) {
                    List<String> as = router.serviceProviderCount(cmd.getArgsList());
                    ans = cmd.replayAnswer(as);
                }
                if (ans != null) {
                    try {
                        ans.sendTo(this);
                    } catch (IOException ex) {
                        Logger.getLogger(Exchanger.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        } else {
            monitor.add(new TaskMonitor(cmd), TIMEOUT, RETRY_TIME);
        }
    }

    public class TaskMonitor implements ITimerMonitorTask {

        ExchangerCommand cmd;

        public TaskMonitor(ExchangerCommand cmd) {
            this.cmd = cmd;
        }

        @Override
        public void start() {
            Exchanger server = router.route(cmd);
            tasks.put(cmd.getNumber(), Exchanger.this);
            if (server != null) {
                try {
                    cmd.sendTo(server);
                } catch (IOException ex) {
                    Logger.getLogger(Exchanger.class.getName()).log(Level.SEVERE, null, ex);
                    sendError(new ExchangerException(ex.getMessage(), ExchangerException.Type.IO_ERROR, ex), cmd);
                }
            } else {
                sendError(ExchangerException.Type.SERVER_NOT_EXIST.create(cmd.getServer() + " not exist.", null), cmd);
            }
        }

        @Override
        public boolean retry() {
            if (tasks.containsKey(cmd.getNumber())) {
                start();
                return false;
            } else {
                return true;
            }
        }

        @Override
        public void fail() {
            tasks.remove(cmd.getNumber());
            sendError(ExchangerException.Type.SERVER_NOT_RESPONSE.create(cmd.getServer() + " not response.", null), cmd);
        }
    }
    ExchangerRouter router;

    public Exchanger(ExchangerRouter router) {
        this.router = router;
    }
}
